.. _Программное окружение:

Программное окружение
=====================

.. index::
    main

Мы видели, что main() - это функция. Выполнение программы начинается с main()
а затем переходит к другим функциям. Определение функции main(), которое мы
мы использовали до сих пор, было следующим:

.. code-block:: d

    void main()
    {
    // ...
    }

Согласно этому определению, main() не принимает никаких параметров и не
возвращает значение. На самом деле в большинстве систем каждая программа обязательно возвращает
значение в свое окружение при завершении работы, которое называется статусом выхода или кодом возврата
код. Поэтому, хотя можно указать тип возврата main()
как void, на самом деле она вернет значение операционной системе или среде запуска

.. index::
    возвращаемое значение функции main

Возвращаемое значение функции main
-----------------------------------

Программы всегда запускаются  в определенной среде. Сущность
которая запускает программу, может быть оболочка, в которой пользователь набирает имя программы
программы и нажимает клавишу Enter, среда разработки, в которой
программист нажимает кнопку [Run], и так далее.
В D и некоторых других языках программирования программа сообщает о своем
о своем статусе выхода из программы в ее окружение с помощью возвращаемого значения функции main().
Точное значение кодов возврата зависит от приложения и системы.
Почти во всех системах нулевое значение возврата означает успешное завершение, в то время как
другие значения обычно означают какой-то сбой. Но есть и исключения,
однако. Например, в OpenVMS четные значения означают неудачу, а нечетные
означают успех. Тем не менее, в большинстве систем можно безопасно использовать значения в диапазоне [0, 127].
безопасно, при этом значения от 1 до 127 имеют значение, характерное для данной программы.
Например, обычная Unix-программа ls, которая используется для перечисления содержимого
каталогов, возвращает 0 в случае успеха, 1 - при незначительных ошибках и 2 - при серьезных.
Во многих средах возвращаемое значение программы, которая была выполнена
в терминале, можно увидеть через переменную окружения $?
Например, когда мы просим ls вывести список несуществующего файла, ее ненулевое возвращаемое значение
можно увидеть с помощью переменной $?, как показано ниже.
*Примечание: В приведенных ниже командных строках строки, начинающиеся с #, обозначают строки, которые набирает пользователь.
строки, которые набирает пользователь. Если вы хотите попробовать выполнить те же команды, вы должны ввести
содержимое этих строк, за исключением символа #. Кроме того, приведенные ниже команды запускают
с именем deneme; замените это имя на имя вашей тестовой программы.
Кроме того, хотя в следующих примерах показано взаимодействие в терминале Linux
терминале, они будут похожими, но не совсем такими же в терминалах других
операционных систем.*


.. code-block:: console

    # ls a_file_that_does_not_exist
    ls: cannot access a_file_that_does_not_exist: No such file     or directory
    # echo $?
    2

**main() всегда возвращает значение**

Некоторые из программ, которые мы написали до сих пор, выбрасывали исключения, когда они
не могли продолжить выполнение своих задач. Как мы уже видели, когда программа завершается с сообщением об ошибке object.Exception
Когда это происходит, даже если функция main() была определена как возвращающая void, автоматически возвращается
ненулевой код состояния автоматически возвращается в окружение программы.
Давайте посмотрим на это в действии в следующей простой программе, которая завершается с исключением
исключением:

.. code-block:: d

    void main()
    {
        throw new Exception("There has been an error.");
    }

Хотя тип возвращаемого значения указан как void, возвращаемое значение ненулевое:

.. code-block:: console

    # ./deneme
        object.Exception: There has been an error.
        ...
    # echo $?
    1

Аналогично, функции void main(), которые завершаются успешно, также автоматически
возвращают ноль в качестве возвращаемого значения. Давайте проверим это на примере следующей программы, которая
успешно завершается:

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("Done!");
    }

Программа возвращает нулевое значение:

.. code-block:: console

    # ./deneme
        Done!
    # echo $?
    0

**Указание возвращаемого значения**

Чтобы выбрать конкретный код возврата, мы возвращаем значение из main() точно так же.
как и из любой другой функции. Тип возвращаемого значения должен быть указан как int
и значение, которое должно быть возвращено оператором return:

.. code-block:: d

    import std.stdio;

    int main()
    {
        int number;
        write("Please enter a number between 3 and 6: ");
        readf(" %s", &number);
        if ((number < 3) || (number > 6))
        {
            stderr.writefln("ERROR: %s is not valid!", number);
            return 111;
        }
        writefln("Thank you for %s.", number);
        return 0;
    }


Если введенное число находится в допустимом диапазоне, возвращаемое значение
программы равно нулю:

.. code-block:: console

    # ./deneme
    Please enter a number between 3 and 6: 5
    Thank you for 5.
    # echo $?
    0

Если число выходит за пределы допустимого диапазона, возвращаемое значение программы будет следующим
111:

.. code-block:: console

    # ./deneme
    Please enter a number between 3 and 6: 10
    ERROR: 10 is not valid!
    # echo $?
    111

Значение 111 в приведенной выше программе является произвольным; обычно в качестве кода отказа используется 1.
код отказа.


.. index::
    stderr
    cтандартный поток ошибок stderr

Стандартный поток ошибок stderr
-------------------------------

В приведенной выше программе используется поток stderr. Этот поток является третьим из
стандартных потоков. Он используется для записи сообщений об ошибках:
* stdin: стандартный поток ввода
* stdout: стандартный поток вывода
* stderr: стандартный поток ошибок
Когда программа запускается в терминале, обычно сообщения, которые записываются
в stdout и stderr, появляются в окне терминала. При необходимости можно
можно перенаправить эти сообщения по отдельности. Этот вопрос выходит за рамки
этой главы, и детали могут отличаться для каждой программы-оболочки.


Параметры функции main()
------------------------

Обычно программы принимают параметры от среды, которая их запустила
их. Например, мы уже передавали имя файла в качестве параметра командной строки
программе ls. В следующей строке есть два параметра командной строки:

.. code-block:: console

    # ls -l deneme
    -rwxr-xr-x 1 acehreli users 460668 Nov 6 20:38 deneme

Набор параметров командной строки и их значения полностью определяются
программой. Каждая программа документирует свое использование, описывается значение каждого
параметра.
Аргументы, используемые при запуске программы на языке D, передаются в программу
main() в виде среза строк. Определить main() как принимающую параметр типа
типа string[] достаточно, чтобы иметь доступ к аргументам программы. Имя
этого параметра принято сокращать как args. Следующая программа выводит
все аргументы, с которыми она была запущена:

.. code-block:: d

    import std.stdio;

    void main(string[] args)
    {
        foreach (i, arg; args)
        {
            writefln("Argument %-3s: %s", i, arg);
        }
    }


Запустим программу с произвольными аргументами:

.. code-block:: console

    # ./deneme some arguments on the command line 42 --an-option
    Argument 0 : ./deneme
    Argument 1 : some
    Argument 2 : arguments
    Argument 3 : on
    Argument 4 : the
    Argument 5 : command
    Argument 6 : line
    Argument 7 : 42
    Argument 8 : --an-option

Почти во всех системах первым аргументом является имя программы в том виде, в котором оно
как оно было введено пользователем. Остальные аргументы появляются в том порядке, в котором они были
введены.
Программа сама решает, как ей использовать аргументы. На
Следующая программа печатает два своих аргумента в обратном порядке:

.. code-block:: d

    import std.stdio;

    int main(string[] args)
    {
        if (args.length != 3)
        {
            stderr.writefln("ERROR! Correct usage:\n" ~ " %s word1 word2", args[0]);
            return 1;
        }
        writeln(args[2], ' ', args[1]);
        return 0;
    }


Программа также показывает правильное использование, если вы не ввели ровно два слова:

.. code-block:: console

    # ./deneme
    ERROR! Correct usage:
    ./deneme word1 word2
    # ./deneme world hello
    hello world

.. index::
    параметры командной строки
    std.getopt

Параметры командной строки и модуль std.getopt
----------------------------------------------

Это все, что нужно знать о параметрах и возвращаемом значении main().
Однако разбор аргументов является повторяющейся задачей. Модуль std.getopt
предназначен для помощи в разборе параметров командной строки программ.
Некоторые параметры, такие как "world" и "hello", приведенные выше, являются чисто данными для программы
для использования. Другие типы параметров называются опциями командной строки и используются для
для изменения поведения программ. Примером параметра командной строки является параметр -
l, который был передан программе ls выше.
Параметры командной строки делают программы более полезными, избавляя пользователя от необходимости
взаимодействия пользователя с программой, чтобы заставить ее вести себя определенным образом. С помощью
опций командной строки, программы можно запускать из программ-скриптов, а их
их поведение может быть определено с помощью опций командной строки.
Хотя синтаксис и значения аргументов командной строки специфичны каждой программы. Их формат в некоторой степени стандартен. Например,
например, в POSIX опции командной строки начинаются с --, за которым следует имя
опции, а значения идут после символов =:

.. code-block:: console

    # ./deneme --an-option=17

Модуль std.getopt упрощает разбор таких опций. Он обладает более широкими возможностями
чем те, что рассмотрены в этом разделе.
Давайте разработаем программу, которая печатает случайные числа. Возьмем минимальное,
максимальное и общее количество этих чисел в качестве аргументов программы. Давайте
для получения этих значений из командной строки используем следующий синтаксис:

.. code-block:: console

    # ./deneme --count=7 --minimum=10 --maximum=15

Функция getopt() разбирает и присваивает эти значения переменным. Аналогично
readf(), адреса переменных должны быть указаны оператором &:

.. code-block:: d

    import std.stdio;
    import std.getopt;
    import std.random;

    void main(string[] args)
    {
        int count;
        int minimum;
        int maximum;
        getopt(args,
            "count", &count,
            "minimum", &minimum,
            "maximum", &maximum);
        foreach (i; 0 .. count)
        {
            write(uniform(minimum, maximum + 1), ' ');
        }
        writeln();
    }

.. code-block:: console

    # ./deneme --count=7 --minimum=10 --maximum=15
    11 11 13 11 14 15 10

Многие опции командной строки большинства программ также имеют более короткий синтаксис. Например,
например, -c может иметь то же значение, что и --count. Такой альтернативный синтаксис для
каждой опции указывается в getopt() после символа \|. Для каждой опции может существовать более
одного ярлыка для каждой опции:

.. code-block:: d

    getopt(args,"count|c", &count,
                "minimum|n", &minimum,
                "maximum|x", &maximum);

В коротких вариантах обычно используется одинарное тире, а символ =
обычно либо опускается, либо заменяется пробелом:

.. code-block:: console

    # ./deneme -c7 -n10 -x15
    11 13 10 15 14 15 14
    # ./deneme -c 7 -n 10 -x 15
    11 13 10 15 14 15 14

getopt() преобразует аргументы из строки в тип каждой переменной. Для
Например, поскольку count выше - это int, getopt() преобразует значение, указанное для
аргумента --count, в int. При необходимости такие преобразования также могут быть
при необходимости такие преобразования могут также выполняться явно командой to.
До сих пор мы использовали std.conv.to только при преобразовании в строку. to, на самом деле, может
фактически, преобразовывать из любого типа в любой тип, если такое преобразование возможно. На
например, следующая программа использует to при преобразовании своего
аргумента в size_t:


.. code-block:: d

    import std.stdio;
    import std.conv;

    void main(string[] args)
    {
        // The default count is 10
        size_t count = 10;
        if (args.length > 1)
        {
            // There is an argument
            count = to!size_t(args[1]);
        }
        foreach (i; 0 .. count)
        {
            write(i * 2, ' ');
        }
        writeln();
    }

Программа выдает 10 чисел, если аргумент не указан:

.. code-block:: console

    # ./deneme
    0 2 4 6 8 10 12 14 16 18
    # ./deneme 3
    0 2 4

Переменные среды
----------------

Среда, в которой запускается программа, обычно содержит некоторые переменные
которые программа может использовать. Доступ к переменным окружения можно получить
через интерфейс ассоциативного массива std.process.environment. Например,
Например, следующая программа выводит переменную окружения PATH:

.. code-block:: d

    import std.process;

    void main()
    {
        writeln(environment["PATH"]);
    }

Вывод

.. code-block:: console

    # ./deneme
    /usr/local/bin:/usr/bin

std.process.environment предоставляет доступ к переменным окружения
через синтаксис ассоциативного массива. Однако сама среда не является
ассоциативным массивом. При необходимости переменные окружения можно преобразовать в
ассоциативный массив с помощью функции toAA():

.. code-block:: d

    string[string] envVars = environment.toAA();

.. index::
    запуск других программ
    executeShell

Запуск других программ
----------------------
Программы могут запускать другие программы и становиться средой для них.
программ. Функция, которая позволяет это сделать, - executeShell из модуля std.process
модуля.
executeShell выполняет свой параметр так, как если бы команда была набрана на
терминале. Затем она возвращает код возврата и вывод этой команды в виде кортежа. Кортежи - это структуры, похожие на массивы, которые мы рассмотрим позже в главе Кортежи
(стр. 513):

.. code-block:: d

    import std.stdio;
    import std.process;

    void main()
    {
        const result = executeShell("ls -l deneme");
        const returnCode = result[0];
        const output = result[1];
        writefln("ls returned %s.", returnCode);
        writefln("Its output:\n%s", output);
    }

Вывод

.. code-block:: console

    ls returned 0.
    Its output:
    -rwxrwxr-x. 1 acehreli acehreli 1359178 Apr 21 15:01 deneme

Резюме
------

* Даже если функция main() определена с типом возврата void, она автоматически
  автоматически возвращает ноль в случае успеха и ненулевое значение в случае неудачи.
* Для вывода сообщений об ошибках подходит stderr.
* main может принимать параметры в виде string[].
* std.getopt помогает разобрать параметры командной строки.
* std.process помогает получить доступ к переменным окружения и запустить другие
  программы.

Упражнения
----------

#. Напишите программу-калькулятор, которая принимает оператор и два операнда в качестве
аргументы командной строки. Пусть программа поддерживает следующие варианты использования:


.. code-block:: console

    # ./deneme 3.4 x 7.8
       26.52

*Примечание: Поскольку символ \* имеет особое значение на большинстве терминалов (точнее
точнее, в большинстве оболочек), я использовал вместо него x. Вы можете по-прежнему использовать \*, если он
как \\*.*

#. Напишите программу, которая спрашивает пользователя, какую программу следует запустить, запускает эту программу и печатает ее вывод.
и печатает ее вывод.
Решения приведены на странице 713.
